#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project ：tools 
@File ：demo.py
@IDE  ：PyCharm 
@Author ：李涵彬
@Date ：2024/12/23 下午3:13 
"""

import tkinter as tk
from tkinter import filedialog, messagebox, ttk

import numpy as np
import pandas as pd
import ast
import logging
import sys
from pathlib import Path
from typing import List, Dict, Union, Optional, Tuple
from threading import Thread
from multiprocessing import Pool, cpu_count
import os

# 导入智能分配算法
try:
	from allocation_algorithm import run_allocation
except ImportError:
	logging.warning("无法导入allocation_algorithm模块，智能分配功能将不可用")
	run_allocation = None

# 日志配置（支持nuitka打包）
if getattr(sys, 'frozen', False):
	# 打包后的exe环境
	LOG_DIR = Path(sys.executable).parent / 'logs'
else:
	# 开发环境
	LOG_DIR = Path(__file__).parent / 'logs'

LOG_DIR.mkdir(exist_ok=True)
LOG_FILE = LOG_DIR / 'app.log'

logging.basicConfig(
	level=logging.INFO,
	format='%(asctime)s - %(levelname)s - %(message)s',
	handlers=[logging.FileHandler(LOG_FILE), logging.StreamHandler()]
)

# 配置文件路径（支持nuitka打包）
# 使用基于脚本所在目录的绝对路径，打包后也能正确找到配置文件
if getattr(sys, 'frozen', False):
	# 打包后的exe环境
	BASE_DIR = Path(sys.executable).parent
else:
	# 开发环境
	BASE_DIR = Path(__file__).parent

CONFIG_DIR = BASE_DIR / 'config'
REDUCTION_WIDTH_CONFIG_PATH = CONFIG_DIR / '热轧产线减宽量配置表.xlsx'
SUBSTITUTE_CONFIG_PATH = CONFIG_DIR / '以优充次配置表.xlsx'


def can_parse_with_ast(code_string):
	"""
	检查给定的代码字符串是否可以被解析为AST（抽象语法树）。

	参数:
	code_string (str): 待检查的代码字符串。

	返回:
	bool: 如果代码字符串可以被解析为AST，则返回True；否则返回False。
	"""
	try:
		# 检查是否为nan或空值
		if pd.isna(code_string) or code_string is None or str(code_string).strip() == '' or str(code_string).lower() == 'nan':
			return False
		# 尝试解析代码字符串
		min_width, max_width = ast.literal_eval(code_string)
		return True
	except (SyntaxError, ValueError, TypeError, AttributeError):
		# 捕获所有可能的解析异常
		return False


def read_excel_file(file_path: Union[str, Path], usecols: Optional[List[str]] = None) -> pd.DataFrame:
	"""
	读取 Excel 文件，并处理可能的异常。
	:param file_path: 文件路径
	:param usecols: 需要加载的列（可选）
	:return: DataFrame
	"""
	try:
		logging.info(f"读取文件: {file_path}")
		return pd.read_excel(file_path, usecols=usecols)
	except Exception as e:
		logging.error(f"无法读取文件 {file_path}: {e}")
		raise


def ensure_dataframe(data: Union[pd.DataFrame, pd.Series]) -> pd.DataFrame:
	"""
	确保数据为DataFrame格式。如果是Series，则转换为DataFrame。
	:param data: DataFrame或Series
	:return: DataFrame
	"""
	if isinstance(data, pd.Series):
		return data.to_frame().T
	return data


def match_contracts_by_substitute_rule(
		contracts_df: pd.DataFrame,
		sub_rule: pd.Series,
		recommend_min_width: Optional[float] = None,
		recommend_max_width: Optional[float] = None
) -> pd.DataFrame:
	"""
	根据以优充次规则匹配合同。
	:param contracts_df: 合同数据
	:param sub_rule: 以优充次规则
	:param recommend_min_width: 推荐最小宽度（可选）
	:param recommend_max_width: 推荐最大宽度（可选）
	:return: 匹配的合同DataFrame
	"""
	if contracts_df.empty:
		return pd.DataFrame()
		
	sub_drawing_code = sub_rule['(合同)出钢标记']
	sub_sign_code = sub_rule['钢级代码']
	min_width = sub_rule['宽度下限']
	max_width = sub_rule['宽度上限']
	min_thickness = sub_rule['厚度下限']
	max_thickness = sub_rule['厚度上限']

	# 构建基础筛选条件（安全检查列是否存在）
	if '出钢标记' not in contracts_df.columns:
		return pd.DataFrame()
		
	conditions = (contracts_df['出钢标记'] == sub_drawing_code)
	
	if '轧宽' in contracts_df.columns:
		conditions &= (
			(contracts_df['轧宽'] >= min_width) &
			(contracts_df['轧宽'] <= max_width)
		)
		
	if '轧厚' in contracts_df.columns:
		conditions &= (
			(contracts_df['轧厚'] >= min_thickness) &
			(contracts_df['轧厚'] <= max_thickness)
		)

	# 如果有推荐轧宽，添加推荐轧宽约束
	if recommend_min_width is not None and recommend_max_width is not None:
		if '轧宽' in contracts_df.columns:
			conditions &= (
				(contracts_df['轧宽'] >= recommend_min_width) &
				(contracts_df['轧宽'] <= recommend_max_width)
			)
		if '轧厚' in contracts_df.columns:
			conditions &= (contracts_df['轧厚'] >= 1.8)

	# 如果有钢级代码，添加钢级代码约束
	if sub_sign_code and sub_sign_code != '' and '钢级代码' in contracts_df.columns:
		conditions &= (contracts_df['钢级代码'] == sub_sign_code)

	return ensure_dataframe(contracts_df[conditions])


def filter_by_width_and_drawing_code(
		material_row: pd.Series,
		contracts_df: pd.DataFrame,
		substitute_config_df: pd.DataFrame,
		reduction_width_config_df: pd.DataFrame
) -> pd.DataFrame:
	"""
	根据宽度和出钢标记筛选合同。
	包括直接匹配和以优充次匹配两种方式。
	:param material_row: 物料行数据
	:param contracts_df: 合同数据
	:param substitute_config_df: 以优充次配置
	:param reduction_width_config_df: 减宽量配置
	:return: 匹配的合同DataFrame
	"""
	material_drawing_code = material_row['出钢标记']
	material_recommend_width = material_row.get('推荐轧宽')
	matched_contracts = pd.DataFrame()

	# 获取以优充次配置
	substitute_rules = substitute_config_df[
		substitute_config_df['(材料)出钢标记'] == material_drawing_code
	]

	# 情况1：有推荐轧宽
	if can_parse_with_ast(str(material_recommend_width).strip()):
		recommend_min_width, recommend_max_width = ast.literal_eval(material_recommend_width)

		# 直接匹配：出钢标记一致且合同轧宽在推荐轧宽范围内
		if '出钢标记' in contracts_df.columns:
			conditions = (contracts_df['出钢标记'] == material_drawing_code)
			if '轧宽' in contracts_df.columns:
				conditions &= (
					(contracts_df['轧宽'] >= recommend_min_width) &
					(contracts_df['轧宽'] <= recommend_max_width)
				)
			if '轧厚' in contracts_df.columns:
				conditions &= (contracts_df['轧厚'] >= 1.8)
			
			direct_matched = contracts_df[conditions]
			matched_contracts = pd.concat([
				matched_contracts,
				ensure_dataframe(direct_matched)
			], ignore_index=True)

		# 以优充次匹配：使用推荐轧宽约束
		for _, sub_rule in substitute_rules.iterrows():
			sub_contracts = match_contracts_by_substitute_rule(
				contracts_df, sub_rule, recommend_min_width, recommend_max_width
			)
			matched_contracts = pd.concat([matched_contracts, sub_contracts], ignore_index=True)

	# 情况2：无推荐轧宽，使用减宽量表
	else:
		# 直接匹配：出钢标记一致
		if '出钢标记' in contracts_df.columns:
			direct_matched = contracts_df[contracts_df['出钢标记'] == material_drawing_code]
		else:
			direct_matched = pd.DataFrame()

		# 以优充次匹配：不使用推荐轧宽约束
		for _, sub_rule in substitute_rules.iterrows():
			sub_contracts = match_contracts_by_substitute_rule(contracts_df, sub_rule)
			direct_matched = pd.concat([direct_matched, sub_contracts], ignore_index=True)

		# 根据减宽量表进一步筛选（优化：收集到列表再一次性创建DataFrame）
		reduction_matched_list = []
		material_width = material_row.get('宽(mm)', 0)
		
		if material_width > 0 and not direct_matched.empty:
			for _, contract in direct_matched.iterrows():
				# 安全获取合同信息
				if '全程途径码' not in contract or '轧宽' not in contract:
					continue
					
				contract_process_code = contract['全程途径码'][8:10]
				contract_width = contract['轧宽']

				# 查找对应产线的减宽量配置（预先筛选减少循环次数）
				process_rules = reduction_width_config_df[
					reduction_width_config_df['产线'] == contract_process_code
				]
				
				for _, reduction_rule in process_rules.iterrows():
					reduction_width = reduction_rule['减宽量']
					min_config = reduction_rule['宽度小值']
					max_config = reduction_rule['宽度大值']

					# 判断合同轧宽是否在配置范围内
					if min_config <= contract_width <= max_config:
						min_width = contract_width
						max_width = contract_width + reduction_width
						# 判断物料宽度是否在计算范围内
						if min_width <= material_width <= max_width:
							reduction_matched_list.append(contract)
							break
		
		# 一次性创建DataFrame而不是多次concat
		if reduction_matched_list:
			reduction_matched = pd.DataFrame(reduction_matched_list)
			matched_contracts = pd.concat([matched_contracts, reduction_matched], ignore_index=True)
		else:
			matched_contracts = pd.concat([matched_contracts, pd.DataFrame()], ignore_index=True)

	return matched_contracts


def filter_by_thickness(contracts_df: pd.DataFrame, material_row: pd.Series) -> pd.DataFrame:
	"""
	根据厚度要求筛选合同。
	如果物料机组特殊指令中包含"4.0mm"，则合同轧厚需要≥4.0。
	:param contracts_df: 待筛选的合同数据
	:param material_row: 物料行数据
	:return: 筛选后的合同DataFrame
	"""
	if contracts_df.empty or '轧厚' not in contracts_df.columns:
		return contracts_df
		
	# 检查是否有4.0mm特殊要求
	if '4.0' in str(material_row.get('机组特殊指令', '')):
		filtered_contracts = contracts_df[contracts_df['轧厚'] >= 4.0]
		return ensure_dataframe(filtered_contracts)
	return contracts_df


def filter_by_weight(contracts_df: pd.DataFrame, material_row: pd.Series) -> pd.DataFrame:
	"""
	根据吨重要求筛选合同。
	支持1-3倍的订货重量范围。
	:param contracts_df: 待筛选的合同数据
	:param material_row: 物料行数据
	:return: 筛选后的合同DataFrame
	"""
	if contracts_df.empty:
		return contracts_df
	
	# 检查必要的列是否存在
	if '订货重量单件最小值' not in contracts_df.columns or '订货重量单件最大值' not in contracts_df.columns:
		return contracts_df
	
	material_weight = material_row.get('重量(t)', 0)
	if material_weight == 0:
		return contracts_df
	
	# 向量化操作：计算1-3倍的重量范围并检查
	min_weights = contracts_df['订货重量单件最小值'].astype(float)
	max_weights = contracts_df['订货重量单件最大值'].astype(float)
	
	# 检查是否在1倍、2倍或3倍范围内
	mask = (
		((min_weights <= material_weight) & (material_weight <= max_weights)) |
		((2 * min_weights <= material_weight) & (material_weight <= 2 * max_weights)) |
		((3 * min_weights <= material_weight) & (material_weight <= 3 * max_weights))
	)
	
	return contracts_df[mask]


def filter_by_spec_without_width(
		material_row: pd.Series,
		contracts_df: pd.DataFrame,
		substitute_config_df: pd.DataFrame
) -> pd.DataFrame:
	"""
	规格筛选（修改版）：只考虑出钢标记、轧厚、钢级代码，不考虑轧宽。
	用于Sheet 3的规格筛选，忽略所有宽度相关的约束（推荐轧宽、宽度下限/上限、减宽量表）。
	:param material_row: 物料行数据
	:param contracts_df: 合同数据
	:param substitute_config_df: 以优充次配置
	:return: 匹配的合同DataFrame
	"""
	if contracts_df.empty:
		return pd.DataFrame()
		
	material_drawing_code = material_row['出钢标记']
	matched_contracts = pd.DataFrame()
	
	# 直接匹配：仅出钢标记一致（不考虑宽度）
	if '出钢标记' in contracts_df.columns:
		conditions = (contracts_df['出钢标记'] == material_drawing_code)
		# 基础厚度要求（>= 1.8）
		if '轧厚' in contracts_df.columns:
			conditions &= (contracts_df['轧厚'] >= 1.8)
		direct_matched = contracts_df[conditions]
		matched_contracts = pd.concat([matched_contracts, direct_matched], ignore_index=True)
	
	# 以优充次匹配：获取配置规则
	substitute_rules = substitute_config_df[
		substitute_config_df['(材料)出钢标记'] == material_drawing_code
	]
	
	# 遍历以优充次规则，匹配合同（只考虑出钢标记、厚度范围、钢级代码，忽略所有宽度约束）
	for _, sub_rule in substitute_rules.iterrows():
		sub_drawing_code = sub_rule['(合同)出钢标记']
		sub_sign_code = sub_rule['钢级代码']
		min_thickness = sub_rule['厚度下限']
		max_thickness = sub_rule['厚度上限']
		
		# 构建筛选条件（仅出钢标记、厚度范围、钢级代码，不含任何宽度约束）
		if '出钢标记' not in contracts_df.columns:
			continue
			
		conditions = (contracts_df['出钢标记'] == sub_drawing_code)
		
		# 厚度范围约束（来自以优充次配置）
		if '轧厚' in contracts_df.columns:
			conditions &= (
				(contracts_df['轧厚'] >= min_thickness) &
				(contracts_df['轧厚'] <= max_thickness) &
				(contracts_df['轧厚'] >= 1.8)  # 基础厚度要求
			)
		
		# 钢级代码约束（如果配置中有要求）
		if sub_sign_code and sub_sign_code != '' and '钢级代码' in contracts_df.columns:
			conditions &= (contracts_df['钢级代码'] == sub_sign_code)
		
		sub_contracts = contracts_df[conditions]
		matched_contracts = pd.concat([matched_contracts, sub_contracts], ignore_index=True)
	
	return matched_contracts


def process_single_material(
		args: Tuple[int, pd.Series, pd.DataFrame, pd.DataFrame, pd.DataFrame, pd.DataFrame]
) -> Tuple[int, Optional[Dict[str, str]], Optional[Dict[str, str]], Optional[Dict[str, str]]]:
	"""
	处理单个物料的合同匹配（用于多进程）。
	:param args: 包含(index, material_row, contracts_df, substitute_config_df, reduction_width_config_df, total_rows)的元组
	:return: (index, full_result, no_weight_result, drawing_code_only_result)
	"""
	index, material_row, contracts_df, substitute_config_df, reduction_width_config_df, total_rows = args
	
	try:
		# 获取物料基本信息（安全访问）
		material_drawing_code = material_row.get('出钢标记')
		material_number = material_row.get('材料号')
		
		# 检查必要的字段
		if not material_drawing_code or not material_number:
			logging.warning(f"物料索引 {index} 缺少必要字段（出钢标记或材料号），跳过处理")
			return (index, None, None, None)
		
		if index % 100 == 0:  # 每100条记录输出一次日志
			logging.info(f"处理物料：{index + 1}/{total_rows}，材料号：{material_number}")
		
		# ========== 三个Sheet的筛选策略 ==========
		# Sheet 1: 筛选1 + 筛选2(完整) + 筛选3 + 筛选4
		# Sheet 2: 筛选1 + 筛选2(完整) + 筛选3
		# Sheet 3: 筛选1 + 筛选2(修改版，不考虑宽度) + 筛选3
		
		# ========== Sheet 1 和 Sheet 2 共享基础（筛选1+2+3） ==========
		# 筛选1+2：出钢标记 + 规格筛选（包含宽度）
		matched_base = filter_by_width_and_drawing_code(
			material_row, contracts_df, substitute_config_df, reduction_width_config_df
		)
		# 筛选3：4.0厚度筛选
		matched_base = filter_by_thickness(matched_base, material_row)
		
		# 删除重复合同（一次性处理）
		if matched_base.shape[0] > 0:
			matched_base = matched_base.drop_duplicates(subset=['合同号'])
		
		# ========== Sheet 2: 筛选1+2+3（无吨重） ==========
		contract_numbers_no_weight = matched_base['合同号'].tolist() if not matched_base.empty and '合同号' in matched_base.columns else []
		result_no_weight = None
		if contract_numbers_no_weight:
			result_no_weight = {
				'材料号': material_number,
				'出钢标记': material_drawing_code,
				'合同号': ','.join(contract_numbers_no_weight)
			}
		
		# ========== Sheet 1: 筛选1+2+3+4（完整筛选） ==========
		# 在Sheet 2基础上添加筛选4：吨重筛选
		matched_full = filter_by_weight(matched_base, material_row)
		contract_numbers_full = matched_full['合同号'].tolist() if not matched_full.empty and '合同号' in matched_full.columns else []
		result_full = None
		if contract_numbers_full:
			result_full = {
				'材料号': material_number,
				'出钢标记': material_drawing_code,
				'合同号': ','.join(contract_numbers_full)
			}
		
		# ========== Sheet 3: 筛选1+2(修改版)+3（无吨重、规格筛选不考虑宽度） ==========
		# 筛选1+2(修改版)：出钢标记 + 规格筛选（不考虑宽度，只考虑轧厚、钢级代码）
		matched_no_width = filter_by_spec_without_width(
			material_row, contracts_df, substitute_config_df
		)
		# 筛选3：4.0厚度筛选
		matched_no_width = filter_by_thickness(matched_no_width, material_row)
		
		if matched_no_width.shape[0] > 0:
			matched_no_width = matched_no_width.drop_duplicates(subset=['合同号'])
		contract_numbers_no_width = matched_no_width['合同号'].tolist() if not matched_no_width.empty and '合同号' in matched_no_width.columns else []
		result_drawing_only = None
		if contract_numbers_no_width:
			result_drawing_only = {
				'材料号': material_number,
				'出钢标记': material_drawing_code,
				'合同号': ','.join(contract_numbers_no_width)
			}
		
		return (index, result_full, result_no_weight, result_drawing_only)
		
	except Exception as e:
		material_number = material_row.get('材料号', 'unknown')
		logging.error(f"处理材料号 {material_number} 时发生错误: {e}")
		return (index, None, None, None)


def process_materials(materials_df: pd.DataFrame, contracts_df: pd.DataFrame, reduction_width_config_df: pd.DataFrame,
				  substitute_config_df: pd.DataFrame, progress_var: tk.DoubleVar, use_multiprocessing: bool = True) -> Dict[str, pd.DataFrame]:
	"""
	处理物料数据并匹配合同号。
	返回3个不同筛选条件的结果集：
	- 'full': Sheet1 完整筛选（筛选1+2+3+4：出钢标记+规格(含宽度)+4.0厚度+吨重）
	- 'no_weight': Sheet2 无吨重筛选（筛选1+2+3：出钢标记+规格(含宽度)+4.0厚度）
	- 'drawing_code_only': Sheet3 规格不含宽度（筛选1+2修改版+3：出钢标记+规格(仅厚度/钢级)+4.0厚度）
	:return: 字典，包含3个不同筛选条件的DataFrame
	"""
	# 以优充次表钢级代码空值处理
	substitute_config_df.fillna(value='', inplace=True)

	total_rows = len(materials_df)
	logging.info(f"开始处理 {total_rows} 条物料数据...")
	
	if use_multiprocessing and total_rows > 10:
		# 使用多进程处理
		num_processes = max(1, cpu_count() - 1)  # 保留一个核心给主进程
		logging.info(f"使用多进程模式，进程数：{num_processes}")
		
		# 准备参数列表
		args_list = [
			(index, row, contracts_df, substitute_config_df, reduction_width_config_df, total_rows)
			for index, row in materials_df.iterrows()
		]
		
		# 使用进程池处理（明确指定spawn上下文以支持打包）
		import multiprocessing
		ctx = multiprocessing.get_context('spawn')
		with ctx.Pool(processes=num_processes) as pool:
			# 使用imap_unordered以便尽快获得结果并更新进度
			results = []
			for i, result in enumerate(pool.imap_unordered(process_single_material, args_list, chunksize=10)):
				results.append(result)
				# 更新进度条
				progress_var.set((i + 1) / total_rows)
			
		logging.info("多进程处理完成，开始合并结果...")
		
		# 合并结果（按原始顺序排序）
		results.sort(key=lambda x: x[0])
		
		result_full = []
		result_no_weight = []
		result_drawing_code_only = []
		
		for index, full_res, no_weight_res, drawing_only_res in results:
			if full_res:
				result_full.append(full_res)
			if no_weight_res:
				result_no_weight.append(no_weight_res)
			if drawing_only_res:
				result_drawing_code_only.append(drawing_only_res)
		
	else:
		# 使用单进程处理（原始逻辑）
		logging.info("使用单进程模式")
		result_full = []
		result_no_weight = []
		result_drawing_code_only = []
		
		for index, material_row in materials_df.iterrows():
			_, full_res, no_weight_res, drawing_only_res = process_single_material(
				(index, material_row, contracts_df, substitute_config_df, reduction_width_config_df, total_rows)
			)
			
			if full_res:
				result_full.append(full_res)
			if no_weight_res:
				result_no_weight.append(no_weight_res)
			if drawing_only_res:
				result_drawing_code_only.append(drawing_only_res)
			
			# 更新进度条
			progress_var.set((index + 1) / total_rows)
	
	logging.info(f"处理完成！Sheet1(1+2+3+4)：{len(result_full)}条，Sheet2(1+2+3)：{len(result_no_weight)}条，Sheet3(1+2修改版+3)：{len(result_drawing_code_only)}条")
	
	# 返回3个结果集
	return {
		'full': pd.DataFrame(result_full),
		'no_weight': pd.DataFrame(result_no_weight),
		'drawing_code_only': pd.DataFrame(result_drawing_code_only)
	}


def clean_input(value: str) -> str:
	"""
	清洗输入字符串，去除非标准字符。
	:param value: 原始值
	:return: 清洗后的值
	"""
	if isinstance(value, str):
		return value.strip().replace('\n', '').replace('\r', '')
	return value


def save_results_to_excel(results_dict: Dict[str, pd.DataFrame], file_path: Union[str, Path]):
	"""
	保存多个结果到 Excel 文件的不同Sheet。
	:param results_dict: 包含多个DataFrame的字典，key为Sheet名称
	:param file_path: 保存路径
	"""
	# 检查是否所有DataFrame都为空
	has_valid_data = any(not df.empty and '合同号' in df.columns for df in results_dict.values())
	
	with pd.ExcelWriter(file_path, engine='openpyxl') as writer:
		saved_count = 0
		for sheet_name, df in results_dict.items():
			# 检查DataFrame是否为空或缺少必要的列
			if not df.empty and '合同号' in df.columns:
				try:
					# 复制DataFrame以避免修改原始数据
					df_copy = df.copy()
					# 拆分合同号列为多列
					split_contract_columns = df_copy['合同号'].str.split(',', expand=True)
					if split_contract_columns is not None and not split_contract_columns.empty:
						# 重命名拆分后的列
						split_contract_columns.columns = [f'合同号{i + 1}' for i in range(split_contract_columns.shape[1])]
						# 删除原始"合同号"列
						df_copy = df_copy.drop('合同号', axis=1)
						# 使用pd.concat一次性添加所有列（避免DataFrame碎片化）
						df_copy = pd.concat([df_copy, split_contract_columns], axis=1)
					else:
						# 如果拆分失败，删除原始"合同号"列
						df_copy = df_copy.drop('合同号', axis=1)
					df_copy.to_excel(writer, sheet_name=sheet_name, index=False)
					logging.info(f"Sheet '{sheet_name}' 已保存，共 {len(df_copy)} 条记录")
					saved_count += 1
				except Exception as e:
					logging.error(f"保存Sheet '{sheet_name}' 时出错: {e}")
			else:
				logging.warning(f"Sheet '{sheet_name}' 为空或缺少'合同号'列，跳过保存")
		
		# 如果所有sheet都为空，创建一个占位sheet避免Excel报错
		if saved_count == 0:
			logging.warning("所有结果集均为空，创建占位Sheet")
			empty_df = pd.DataFrame({'说明': ['没有匹配到任何合同，请检查物料数据和合同数据']})
			empty_df.to_excel(writer, sheet_name='无匹配结果', index=False)
	
	logging.info(f"所有结果已保存到 {file_path}")


def main(contract_path: Union[str, Path], material_path: Union[str, Path],
		 reduction_width_config_path: Union[str, Path], substitute_config_path: Union[str, Path],
		 progress_var: tk.DoubleVar):
	"""
	主处理函数。
	"""
	try:
		# 合同表
		contracts_df = read_excel_file(contract_path)
		# 物料表
		materials_df = read_excel_file(material_path)
		# 减宽量配置表
		reduction_width_config_df = read_excel_file(reduction_width_config_path)
		# 以优充次配置表
		substitute_config_df = read_excel_file(substitute_config_path)

		# 检查物料数据必要的列
		required_material_columns = ['材料号', '出钢标记']
		missing_material_columns = [col for col in required_material_columns if col not in materials_df.columns]
		if missing_material_columns:
			error_msg = f"物料数据缺少必要的列：{', '.join(missing_material_columns)}\n当前列：{', '.join(materials_df.columns.tolist())}"
			logging.error(error_msg)
			messagebox.showerror("数据错误", error_msg)
			return
		
		# 检查合同数据必要的列
		required_contract_columns = ['合同号', '出钢标记']
		missing_contract_columns = [col for col in required_contract_columns if col not in contracts_df.columns]
		if missing_contract_columns:
			error_msg = f"合同数据缺少必要的列：{', '.join(missing_contract_columns)}\n当前列：{', '.join(contracts_df.columns.tolist())}"
			logging.error(error_msg)
			messagebox.showerror("数据错误", error_msg)
			return

		# 处理物料并获取3个结果集
		results = process_materials(materials_df, contracts_df, reduction_width_config_df, substitute_config_df,
									 progress_var)

		# 准备保存的结果字典，指定Sheet名称
		results_to_save = {
			'完整筛选': results['full'],
			'无吨重筛选': results['no_weight'],
			'规格不含宽度': results['drawing_code_only']
		}

		# 保存到Excel文件的多个Sheet
		result_path = Path.home() / 'Desktop' / 'result.xlsx'
		save_results_to_excel(results_to_save, result_path)
		
		# 显示统计信息
		summary = f"处理完成！结果已保存到：{result_path}\n\n"
		summary += f"Sheet1-完整筛选（筛选1+2+3+4）：{len(results['full'])} 条\n"
		summary += f"Sheet2-无吨重筛选（筛选1+2+3）：{len(results['no_weight'])} 条\n"
		summary += f"Sheet3-规格不含宽度（筛选1+2修改版+3）：{len(results['drawing_code_only'])} 条\n\n"
		# summary += "说明：\n"
		# summary += "筛选1=出钢标记，筛选2=规格(宽度/厚度/钢级)，筛选3=4.0厚度，筛选4=吨重\n"
		# summary += "Sheet3的筛选2修改版：仅考虑厚度/钢级代码，不考虑宽度"
		messagebox.showinfo("完成", summary)

	except Exception as e:
		logging.error(f"处理失败: {e}")
		messagebox.showerror("错误", f"处理失败: {e}")


def browse_file() -> str:
	"""
	打开文件选择对话框并返回文件路径。
	:return: 文件路径
	"""
	file_path = filedialog.askopenfilename(filetypes=[("Excel 文件", "*.xlsx"), ("所有文件", "*.*")])
	return file_path


def start_processing(contract_path: str, material_path: str, progress_var: tk.DoubleVar):
	"""
	启动处理线程。
	"""
	if not contract_path or not material_path:
		messagebox.showwarning("警告", "请先选择合同和物料文件！")
		return

	progress_var.set(0.0)

	thread = Thread(
		target=main,
		args=(contract_path, material_path, REDUCTION_WIDTH_CONFIG_PATH, SUBSTITUTE_CONFIG_PATH, progress_var),
		daemon=True
	)
	thread.start()


def start_allocation(progress_var: tk.DoubleVar):
	"""
	启动智能分配线程。
	"""
	if run_allocation is None:
		messagebox.showerror("错误", "智能分配模块未加载，请检查allocation_algorithm.py文件是否存在")
		return

	# 检查result.xlsx是否存在
	desktop = Path.home() / 'Desktop'
	result_file = desktop / 'result.xlsx'

	if not result_file.exists():
		messagebox.showwarning("警告", "未找到result.xlsx文件！\n请先执行合同匹配，生成result.xlsx文件。")
		return

	# 确认对话框
	confirm = messagebox.askyesno(
		"确认", 
		f"将使用以下文件进行智能分配：\n{result_file}\n\n是否继续？"
	)

	if not confirm:
		return

	progress_var.set(0.0)

	# 在新线程中执行分配
	def run_allocation_task():
		try:
			progress_var.set(0.3)
			logging.info("开始智能分配...")

			success = run_allocation(result_file)

			progress_var.set(1.0)

			if success:
				messagebox.showinfo(
					"完成", 
					"智能分配完成！\n结果已保存到桌面的allocation_result_*.xlsx文件中。"
				)
			else:
				messagebox.showerror("错误", "智能分配失败，请查看日志文件获取详细信息。")
		except Exception as e:
			logging.error(f"智能分配过程中发生错误：{e}", exc_info=True)
			messagebox.showerror("错误", f"智能分配失败：{str(e)}")
		finally:
			progress_var.set(0.0)

	thread = Thread(target=run_allocation_task, daemon=True)
	thread.start()


def create_gui():
	"""
	创建主界面。
	"""
	root = tk.Tk()
	root.title("合同匹配工具")
	root.geometry("900x380")
	font_size = 14

	# 文件选择
	contract_path_var = tk.StringVar()
	material_path_var = tk.StringVar()

	ttk.Label(root, font=("Arial", font_size), text="选择合同文件：").grid(row=0, column=0, padx=10, pady=10,
																		  sticky=tk.W)
	ttk.Entry(root, font=("Arial", font_size), textvariable=contract_path_var, width=50).grid(row=0, column=1, padx=10)
	ttk.Button(root, text="浏览", command=lambda: contract_path_var.set(browse_file())).grid(row=0, column=2, padx=10)

	ttk.Label(root, font=("Arial", font_size), text="选择物料文件：").grid(row=1, column=0, padx=10, pady=10,
																		  sticky=tk.W)
	ttk.Entry(root, font=("Arial", font_size), textvariable=material_path_var, width=50).grid(row=1, column=1, padx=10)
	ttk.Button(root, text="浏览", command=lambda: material_path_var.set(browse_file())).grid(row=1, column=2, padx=10)

	# 进度条
	progress_var = tk.DoubleVar()
	ttk.Label(root, font=("Arial", font_size), text="处理进度：").grid(row=2, column=0, padx=10, pady=10, sticky=tk.W)
	progress_bar = ttk.Progressbar(root, variable=progress_var, maximum=1.0)
	progress_bar.grid(row=2, column=1, columnspan=2, padx=10, pady=10, sticky=tk.W + tk.E)

	# 分隔线
	ttk.Separator(root, orient='horizontal').grid(row=3, column=0, columnspan=3, sticky='ew', padx=10, pady=15)

	# 按钮区域
	button_frame = ttk.Frame(root)
	button_frame.grid(row=4, column=0, columnspan=3, pady=10)

	# 合同匹配按钮
	ttk.Button(
		button_frame, 
		text="1. 合同匹配",
		width=20,
		command=lambda: start_processing(contract_path_var.get(), material_path_var.get(), progress_var)
	).pack(side=tk.LEFT, padx=10)

	# 智能分配按钮
	allocation_button = ttk.Button(
		button_frame,
		text="2. 智能分配",
		width=20,
		command=lambda: start_allocation(progress_var)
	)
	allocation_button.pack(side=tk.LEFT, padx=10)
	
	# 如果智能分配模块未加载，禁用按钮
	if run_allocation is None:
		allocation_button.config(state='disabled')

	# 说明文字
	help_text = "说明：\n1. 先选择文件并点击「合同匹配」生成result.xlsx\n2. 再点击「智能分配」进行物料优化分配"
	ttk.Label(root, font=("Arial", 11), text=help_text, foreground="gray").grid(
		row=5, column=0, columnspan=3, pady=10
	)

	root.mainloop()


if __name__ == '__main__':
	# 多进程支持（Windows平台和nuitka打包必需）
	from multiprocessing import freeze_support
	freeze_support()
	
	# 检查配置文件是否存在
	if not REDUCTION_WIDTH_CONFIG_PATH.exists() or not SUBSTITUTE_CONFIG_PATH.exists():
		logging.error("缺少配置文件，请检查 'config' 文件夹中的配置。")
		logging.error("当前路径：" + str(Path.cwd()))
		logging.error("配置文件路径：" + str(Path.cwd() / 'config'))
		messagebox.showerror("错误", "缺少配置文件，请检查 'config' 文件夹。")
	else:
		logging.info("配置文件已加载!")
		logging.info("当前路径：" + str(Path.cwd()))
		logging.info(f"配置文件路径：{REDUCTION_WIDTH_CONFIG_PATH}")
		logging.info(f"配置文件路径：{SUBSTITUTE_CONFIG_PATH}")
		logging.info("开始运行...")
		create_gui()
